@ctrl-ui/react 0.8.17 → 0.8.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/blocks/coding-agent.js +1 -1
  2. package/dist/blocks/design-canvas-inspector.js +1 -1
  3. package/dist/blocks/design-canvas.js +9 -5
  4. package/dist/blocks/discussion-markdown-input.d.ts +12 -0
  5. package/dist/blocks/discussion-markdown-input.js +34 -0
  6. package/dist/blocks/discussion.d.ts +7 -3
  7. package/dist/blocks/discussion.js +77 -16
  8. package/dist/blocks/file-explorer.js +4 -4
  9. package/dist/blocks/settings.js +5 -3
  10. package/dist/knob-contracts/label-knobs.d.ts +4 -0
  11. package/dist/knob-contracts/label-knobs.js +4 -0
  12. package/dist/knob-contracts/markdown-editor-knobs.d.ts +4 -0
  13. package/dist/knob-contracts/markdown-editor-knobs.js +16 -0
  14. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  15. package/dist/knob-contracts/switch-knobs.js +1 -0
  16. package/dist/markdown-editor/context.d.ts +22 -0
  17. package/dist/markdown-editor/context.js +10 -0
  18. package/dist/markdown-editor/extensions.d.ts +10 -0
  19. package/dist/markdown-editor/extensions.js +139 -0
  20. package/dist/markdown-editor/toolbar.d.ts +9 -0
  21. package/dist/markdown-editor/toolbar.js +336 -0
  22. package/dist/markdown-editor/uploads.d.ts +23 -0
  23. package/dist/markdown-editor/uploads.js +32 -0
  24. package/dist/markdown-editor/use-image-uploads.d.ts +12 -0
  25. package/dist/markdown-editor/use-image-uploads.js +120 -0
  26. package/dist/markdown-editor.d.ts +27 -0
  27. package/dist/markdown-editor.js +192 -0
  28. package/dist/styles/index.css +4 -2
  29. package/dist/styles/recipes/code.css +4 -0
  30. package/dist/styles/recipes/field.css +1 -1
  31. package/dist/styles/recipes/label.css +19 -0
  32. package/dist/styles/recipes/markdown-editor.css +196 -0
  33. package/dist/styles/recipes/switch.css +17 -5
  34. package/dist/styles/theme.css +25 -0
  35. package/dist/styles/type-scale.css +28 -2
  36. package/dist/ui/app-shell.js +1 -1
  37. package/dist/ui/autocomplete.js +1 -1
  38. package/dist/ui/code.js +1 -0
  39. package/dist/ui/combobox.js +1 -1
  40. package/dist/ui/command.js +1 -1
  41. package/dist/ui/dockable-panel.js +1 -1
  42. package/dist/ui/label.d.ts +8 -0
  43. package/dist/ui/label.js +14 -0
  44. package/dist/ui/markdown.d.ts +2 -1
  45. package/dist/ui/markdown.js +3 -3
  46. package/dist/ui/responsive-dialog.js +1 -1
  47. package/dist/ui/sidebar-menu.js +1 -1
  48. package/dist/ui/sidebar.js +1 -1
  49. package/dist/ui/typography.d.ts +44 -0
  50. package/dist/ui/typography.js +59 -0
  51. package/dist/ui/use-switch-drag.js +1 -1
  52. package/package.json +37 -1
@@ -215,7 +215,7 @@ function CodingAgentEmptyState({ title = "What should we build?", description, i
215
215
  })
216
216
  }),
217
217
  /* @__PURE__ */ jsx("h2", {
218
- className: "text-balance text-heading-3 sm:text-heading-2",
218
+ className: "text-heading-3 sm:text-heading-2",
219
219
  children: title
220
220
  }),
221
221
  description ? /* @__PURE__ */ jsx("p", {
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import { Button } from "../ui/button.js";
3
3
  import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
4
- import { resizeDesignCanvasLayer } from "./design-canvas-data.js";
5
4
  import { NumberField, NumberFieldGroup, NumberFieldInput, NumberFieldScrubArea } from "../ui/number-field.js";
6
5
  import { ColorPicker, ColorPickerArea, ColorPickerContent, ColorPickerEyeDropper, ColorPickerHue, ColorPickerInput, ColorPickerTrigger } from "../ui/color-picker.js";
7
6
  import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "../ui/empty.js";
8
7
  import { Separator } from "../ui/separator.js";
9
8
  import { Toggle } from "../ui/toggle.js";
9
+ import { resizeDesignCanvasLayer } from "./design-canvas-data.js";
10
10
  import { useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { BlendIcon, EyeIcon, EyeOffIcon, Link2Icon, MinusIcon, PlusIcon, RotateCwIcon, SquareRoundCornerIcon, Unlink2Icon } from "lucide-react";
@@ -3,12 +3,13 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
5
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip.js";
6
- import { createDesignCanvasLayer, designCanvasFillColor, drawnDesignCanvasBounds } from "./design-canvas-data.js";
7
- import { DesignCanvasInspector } from "./design-canvas-inspector.js";
6
+ import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "../ui/toolbar.js";
8
7
  import { Drawer, DrawerContent, DrawerTitle } from "../ui/drawer.js";
9
8
  import { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem } from "../ui/infinite-canvas.js";
10
9
  import { ResizableFloatingPanel } from "../ui/resizable.js";
11
- import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "../ui/toolbar.js";
10
+ import { Text } from "../ui/typography.js";
11
+ import { createDesignCanvasLayer, designCanvasFillColor, drawnDesignCanvasBounds } from "./design-canvas-data.js";
12
+ import { DesignCanvasInspector } from "./design-canvas-inspector.js";
12
13
  import { useRef, useState } from "react";
13
14
  import { jsx, jsxs } from "react/jsx-runtime";
14
15
  import { CircleIcon, FrameIcon, LayersIcon, MousePointer2Icon, SlidersHorizontalIcon, SquareIcon, TypeIcon } from "lucide-react";
@@ -414,8 +415,11 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
414
415
  className: "absolute start-0 bottom-full mb-1 whitespace-nowrap text-caption text-muted-foreground",
415
416
  children: layer.name
416
417
  }) : null,
417
- layer.kind === "text" ? /* @__PURE__ */ jsx("p", {
418
- className: "size-full overflow-hidden text-2xl leading-8 whitespace-nowrap",
418
+ layer.kind === "text" ? /* @__PURE__ */ jsx(Text, {
419
+ as: "p",
420
+ size: "heading-2",
421
+ weight: "normal",
422
+ className: "size-full overflow-hidden whitespace-nowrap",
419
423
  style: { color: fillColor },
420
424
  children: layer.text
421
425
  }) : /* @__PURE__ */ jsx("div", {
@@ -0,0 +1,12 @@
1
+ import { MarkdownImageUploader } from "../markdown-editor/uploads.js";
2
+ import { MarkdownEditorStatus } from "../markdown-editor.js";
3
+ //#region src/blocks/discussion-markdown-input.d.ts
4
+ export default function DiscussionMarkdownInput({ label, placeholder, autoFocus, onUploadImage, onStatusChange, blocked }: {
5
+ label: string;
6
+ placeholder?: string;
7
+ autoFocus?: boolean;
8
+ onUploadImage?: MarkdownImageUploader;
9
+ onStatusChange: (status: MarkdownEditorStatus) => void;
10
+ blocked: boolean;
11
+ }): import("react").JSX.Element;
12
+ //#endregion
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { useChatComposerContext } from "../chat-composer.js";
3
+ import { MarkdownEditor, MarkdownEditorContent } from "../markdown-editor.js";
4
+ import { MarkdownEditorToolbar, MarkdownEditorUploads } from "../markdown-editor/toolbar.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/blocks/discussion-markdown-input.tsx
7
+ function DiscussionMarkdownInput({ label, placeholder, autoFocus, onUploadImage, onStatusChange, blocked }) {
8
+ const composer = useChatComposerContext();
9
+ return /* @__PURE__ */ jsxs(MarkdownEditor, {
10
+ value: composer.value,
11
+ onValueChange: composer.setValue,
12
+ disabled: composer.isDisabled || composer.isLocked,
13
+ onUploadImage,
14
+ onStatusChange,
15
+ onKeyDownCapture: (event) => {
16
+ if (!(event.target instanceof HTMLElement) || !event.target.isContentEditable && event.target.tagName !== "TEXTAREA") return;
17
+ if (event.key !== "Enter" || !(event.metaKey || event.ctrlKey) || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
18
+ event.preventDefault();
19
+ event.stopPropagation();
20
+ if (!blocked) composer.submit();
21
+ },
22
+ children: [
23
+ /* @__PURE__ */ jsx(MarkdownEditorToolbar, {}),
24
+ /* @__PURE__ */ jsx(MarkdownEditorContent, {
25
+ label,
26
+ placeholder,
27
+ autoFocus
28
+ }),
29
+ /* @__PURE__ */ jsx(MarkdownEditorUploads, {})
30
+ ]
31
+ });
32
+ }
33
+ //#endregion
34
+ export { DiscussionMarkdownInput as default };
@@ -1,3 +1,4 @@
1
+ import { MarkdownImageUploader } from "../markdown-editor/uploads.js";
1
2
  import { ChatComposer } from "../chat-composer.js";
2
3
  import { ChatMessage } from "../chat-message.js";
3
4
  import { ComponentProps, ReactNode } from "react";
@@ -9,20 +10,23 @@ export type DiscussionComposerProps = Omit<ComponentProps<typeof ChatComposer>,
9
10
  secondaryAction?: ReactNode;
10
11
  hint?: ReactNode;
11
12
  autoFocus?: boolean;
13
+ format?: "plain" | "markdown";
14
+ onUploadImage?: MarkdownImageUploader;
12
15
  };
13
- export declare function DiscussionComposer({ label, placeholder, submitLabel, secondaryAction, hint, autoFocus, ...props }: DiscussionComposerProps): import("react").JSX.Element;
16
+ export declare function DiscussionComposer({ label, placeholder, submitLabel, secondaryAction, hint, autoFocus, format, onUploadImage, onSubmit, ...props }: DiscussionComposerProps): import("react").JSX.Element;
14
17
  export type DiscussionCommentProps = Omit<ComponentProps<typeof ChatMessage>, "from" | "layout" | "authorLabel" | "children"> & {
15
18
  author: string;
16
19
  avatar?: ReactNode;
17
20
  sentAt: string;
18
21
  timeLabel: ReactNode;
19
22
  edited?: boolean;
20
- children: ReactNode;
23
+ children?: ReactNode;
24
+ markdown?: string;
21
25
  actions?: ReactNode;
22
26
  onReply?: () => void;
23
27
  replyLabel?: ReactNode;
24
28
  replies?: ReactNode;
25
29
  replyComposer?: ReactNode;
26
30
  };
27
- export declare function DiscussionComment({ author, avatar, sentAt, timeLabel, edited, children, actions, onReply, replyLabel, replies, replyComposer, ...props }: DiscussionCommentProps): import("react").JSX.Element;
31
+ export declare function DiscussionComment({ author, avatar, sentAt, timeLabel, edited, children, markdown, actions, onReply, replyLabel, replies, replyComposer, ...props }: DiscussionCommentProps): import("react").JSX.Element;
28
32
  //#endregion
@@ -3,32 +3,90 @@ import { Button } from "../ui/button.js";
3
3
  import { ChatComposer, ChatComposerShell, ChatComposerSubmit, ChatComposerTextarea, ChatComposerToolbar } from "../chat-composer.js";
4
4
  import { ChatMessageReplies } from "../chat-message/social.js";
5
5
  import { ChatMessage, ChatMessageActions, ChatMessageAuthor, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageFooter, ChatMessageHeader, ChatMessageRow, ChatMessageTime } from "../chat-message.js";
6
+ import { Markdown } from "../ui/markdown.js";
6
7
  import { Kbd, KbdGroup } from "../ui/kbd.js";
7
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { Suspense, lazy, useState } from "react";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
10
  import { CornerDownRightIcon } from "lucide-react";
9
11
  //#region src/blocks/discussion.tsx
10
- function DiscussionComposer({ label, placeholder, submitLabel = "Post", secondaryAction, hint = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(KbdGroup, { children: [/* @__PURE__ */ jsx(Kbd, { children: "⌘" }), /* @__PURE__ */ jsx(Kbd, { children: "Enter" })] }), /* @__PURE__ */ jsx("span", { children: "to post" })] }), autoFocus, ...props }) {
12
+ const DiscussionMarkdownInput = lazy(() => import("./discussion-markdown-input.js"));
13
+ function DiscussionComposer({ label, placeholder, submitLabel = "Post", secondaryAction, hint = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(KbdGroup, { children: [/* @__PURE__ */ jsx(Kbd, { children: "⌘" }), /* @__PURE__ */ jsx(Kbd, { children: "Enter" })] }), /* @__PURE__ */ jsx("span", { children: "to post" })] }), autoFocus, format = "plain", onUploadImage, onSubmit, ...props }) {
14
+ const [draft, setDraft] = useState(props.defaultValue ?? "");
15
+ const value = props.value ?? draft;
16
+ const [status, setStatus] = useState({
17
+ isEmpty: false,
18
+ hasPendingUploads: false
19
+ });
20
+ const [submitting, setSubmitting] = useState(false);
21
+ const [error, setError] = useState(null);
22
+ const blocked = format === "markdown" && (status.isEmpty || status.hasPendingUploads);
11
23
  return /* @__PURE__ */ jsx(ChatComposer, {
12
24
  submitKey: "mod-enter",
13
25
  ...props,
14
- children: /* @__PURE__ */ jsxs(ChatComposerShell, { children: [/* @__PURE__ */ jsx(ChatComposerTextarea, {
15
- "aria-label": label,
16
- placeholder,
17
- autoFocus,
18
- "aria-keyshortcuts": "Meta+Enter Control+Enter"
19
- }), /* @__PURE__ */ jsxs(ChatComposerToolbar, { children: [/* @__PURE__ */ jsx("span", {
20
- className: "flex min-w-0 items-center gap-1.5 text-caption text-muted-foreground",
21
- children: hint
22
- }), /* @__PURE__ */ jsxs("span", {
23
- className: "flex items-center gap-2",
24
- children: [secondaryAction, /* @__PURE__ */ jsx(ChatComposerSubmit, { children: submitLabel })]
25
- })] })] })
26
+ value,
27
+ onValueChange: (next) => {
28
+ setDraft(next);
29
+ props.onValueChange?.(next);
30
+ },
31
+ state: submitting ? "submitting" : props.state,
32
+ onSubmit: async (payload) => {
33
+ if (blocked || submitting) return;
34
+ setSubmitting(true);
35
+ setError(null);
36
+ try {
37
+ await onSubmit?.({
38
+ ...payload,
39
+ value: format === "markdown" ? value : payload.value
40
+ });
41
+ } catch (cause) {
42
+ setError(cause instanceof Error ? cause.message : "Couldn't post your comment. Please try again.");
43
+ } finally {
44
+ setSubmitting(false);
45
+ }
46
+ },
47
+ children: /* @__PURE__ */ jsxs(ChatComposerShell, { children: [
48
+ format === "markdown" ? /* @__PURE__ */ jsx(Suspense, {
49
+ fallback: /* @__PURE__ */ jsx(ChatComposerTextarea, {
50
+ "aria-label": label,
51
+ placeholder,
52
+ disabled: true
53
+ }),
54
+ children: /* @__PURE__ */ jsx(DiscussionMarkdownInput, {
55
+ label,
56
+ placeholder,
57
+ autoFocus,
58
+ onUploadImage,
59
+ onStatusChange: setStatus,
60
+ blocked
61
+ })
62
+ }) : /* @__PURE__ */ jsx(ChatComposerTextarea, {
63
+ "aria-label": label,
64
+ placeholder,
65
+ autoFocus,
66
+ "aria-keyshortcuts": "Meta+Enter Control+Enter"
67
+ }),
68
+ error ? /* @__PURE__ */ jsx("p", {
69
+ role: "alert",
70
+ className: "px-3",
71
+ children: error
72
+ }) : null,
73
+ /* @__PURE__ */ jsxs(ChatComposerToolbar, { children: [/* @__PURE__ */ jsx("span", {
74
+ className: "flex min-w-0 items-center gap-1.5 text-caption text-muted-foreground",
75
+ children: hint
76
+ }), /* @__PURE__ */ jsxs("span", {
77
+ className: "flex items-center gap-2",
78
+ children: [secondaryAction, /* @__PURE__ */ jsx(ChatComposerSubmit, {
79
+ disabled: blocked || submitting,
80
+ children: submitLabel
81
+ })]
82
+ })] })
83
+ ] })
26
84
  });
27
85
  }
28
86
  function initials(name) {
29
87
  return name.split(/\s+/).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
30
88
  }
31
- function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false, children, actions, onReply, replyLabel = "Reply", replies, replyComposer, ...props }) {
89
+ function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false, children, markdown, actions, onReply, replyLabel = "Reply", replies, replyComposer, ...props }) {
32
90
  const hasReplies = Boolean(replies) || Boolean(replyComposer);
33
91
  return /* @__PURE__ */ jsx(ChatMessage, {
34
92
  from: "participant",
@@ -49,7 +107,10 @@ function DiscussionComment({ author, avatar, sentAt, timeLabel, edited = false,
49
107
  }),
50
108
  edited ? /* @__PURE__ */ jsx("span", { children: "(edited)" }) : null
51
109
  ] }),
52
- /* @__PURE__ */ jsx(ChatMessageContent, { children }),
110
+ /* @__PURE__ */ jsx(ChatMessageContent, { children: markdown !== void 0 ? /* @__PURE__ */ jsx(Markdown, {
111
+ content: markdown,
112
+ mode: "static"
113
+ }) : children }),
53
114
  onReply ? /* @__PURE__ */ jsx(ChatMessageFooter, { children: /* @__PURE__ */ jsxs(Button, {
54
115
  variant: "ghost",
55
116
  size: "xs",
@@ -8,12 +8,12 @@ import { Input } from "../ui/input.js";
8
8
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
9
9
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
10
10
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
11
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
11
12
  import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
12
13
  import { Drawer, DrawerBody, DrawerContent, DrawerTitle, DrawerTrigger } from "../ui/drawer.js";
14
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
13
15
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable.js";
14
16
  import { groupFileExplorerItems, orderFileExplorerEntries, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
15
- import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
16
- import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
17
17
  import { Fragment, useEffect, useId, useRef, useState } from "react";
18
18
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
19
19
  import { ChevronLeftIcon, ChevronRightIcon, FileIcon, FolderIcon, MoreHorizontalIcon, SearchIcon, XIcon } from "lucide-react";
@@ -395,7 +395,7 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs,
395
395
  }), /* @__PURE__ */ jsx(DrawerContent, { children: selectedEntry ? /* @__PURE__ */ jsx(DrawerBody, { children: /* @__PURE__ */ jsx(FileExplorerPreviewContent, {
396
396
  entry: selectedEntry,
397
397
  title: /* @__PURE__ */ jsx(DrawerTitle, {
398
- className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
398
+ className: "mt-4 max-w-full truncate text-heading-4",
399
399
  children: selectedEntry.name
400
400
  })
401
401
  }) }) : null })] })]
@@ -533,7 +533,7 @@ function FileExplorerPreview({ entry, headingAs: Heading }) {
533
533
  entry,
534
534
  className: "px-6 py-10",
535
535
  title: /* @__PURE__ */ jsx(Heading, {
536
- className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
536
+ className: "mt-4 max-w-full truncate text-heading-4",
537
537
  title: entry.name,
538
538
  children: entry.name
539
539
  })
@@ -10,8 +10,9 @@ import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
10
10
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
11
11
  import { Sidebar, SidebarContent, SidebarHeader, SidebarInset, SidebarTrigger } from "../ui/sidebar.js";
12
12
  import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
13
- import { searchSettings } from "./settings-data.js";
14
13
  import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "../ui/field.js";
14
+ import { Heading } from "../ui/typography.js";
15
+ import { searchSettings } from "./settings-data.js";
15
16
  import { useEffect, useId, useState } from "react";
16
17
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
18
  import { ArrowLeftIcon, SearchIcon, XIcon } from "lucide-react";
@@ -212,8 +213,9 @@ function SettingsPage({ page, instanceId }) {
212
213
  const { layout } = useSidebar();
213
214
  return /* @__PURE__ */ jsxs("div", {
214
215
  className: "mx-auto flex w-full max-w-4xl flex-col gap-10 px-5 py-8 md:px-10 md:py-12",
215
- children: [/* @__PURE__ */ jsx(layout === "contained" ? "h2" : "h1", {
216
- className: "text-heading-2 text-foreground text-balance",
216
+ children: [/* @__PURE__ */ jsx(Heading, {
217
+ level: layout === "contained" ? 2 : 1,
218
+ size: "heading-2",
217
219
  children: page.title
218
220
  }), page.sections.map((section) => /* @__PURE__ */ jsxs(FieldSet, {
219
221
  className: "gap-3",
@@ -0,0 +1,4 @@
1
+ //#region src/knob-contracts/label-knobs.d.ts
2
+ export declare const labelKnobs: readonly ["--cui-label-root-foreground"];
3
+ export type LabelKnobStyle = Partial<Record<(typeof labelKnobs)[number], string>>;
4
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/knob-contracts/label-knobs.ts
2
+ const labelKnobs = ["--cui-label-root-foreground"];
3
+ //#endregion
4
+ export { labelKnobs };
@@ -0,0 +1,4 @@
1
+ //#region src/knob-contracts/markdown-editor-knobs.d.ts
2
+ export declare const markdownEditorKnobs: readonly ["--cui-markdown-editor-background", "--cui-markdown-editor-foreground", "--cui-markdown-editor-border-color", "--cui-markdown-editor-radius", "--cui-markdown-editor-padding", "--cui-markdown-editor-min-height", "--cui-markdown-editor-max-height", "--cui-markdown-editor-gap", "--cui-markdown-editor-muted-foreground", "--cui-markdown-editor-code-background", "--cui-markdown-editor-border-width"];
3
+ export type MarkdownEditorKnobStyle = Partial<Record<(typeof markdownEditorKnobs)[number], string>>;
4
+ //#endregion
@@ -0,0 +1,16 @@
1
+ //#region src/knob-contracts/markdown-editor-knobs.ts
2
+ const markdownEditorKnobs = [
3
+ "--cui-markdown-editor-background",
4
+ "--cui-markdown-editor-foreground",
5
+ "--cui-markdown-editor-border-color",
6
+ "--cui-markdown-editor-radius",
7
+ "--cui-markdown-editor-padding",
8
+ "--cui-markdown-editor-min-height",
9
+ "--cui-markdown-editor-max-height",
10
+ "--cui-markdown-editor-gap",
11
+ "--cui-markdown-editor-muted-foreground",
12
+ "--cui-markdown-editor-code-background",
13
+ "--cui-markdown-editor-border-width"
14
+ ];
15
+ //#endregion
16
+ export { markdownEditorKnobs };
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/switch-knobs.d.ts
2
- export declare const switchKnobs: readonly ["--cui-switch-thumb-shadow", "--cui-switch-radius", "--cui-switch-shadow", "--cui-switch-background", "--cui-switch-border-color", "--cui-switch-border-width", "--cui-switch-checked-border-color", "--cui-switch-hover-background", "--cui-switch-checked-background", "--cui-switch-checked-hover-background", "--cui-switch-thumb-radius", "--cui-switch-thumb-background", "--cui-switch-inline-size", "--cui-switch-height", "--cui-switch-padding", "--cui-switch-thumb-inline-size", "--cui-switch-thumb-press-scale", "--cui-switch-thumb-backdrop-filter"];
2
+ export declare const switchKnobs: readonly ["--cui-switch-thumb-shadow", "--cui-switch-radius", "--cui-switch-shadow", "--cui-switch-background", "--cui-switch-border-color", "--cui-switch-border-width", "--cui-switch-checked-border-color", "--cui-switch-hover-background", "--cui-switch-checked-background", "--cui-switch-checked-hover-background", "--cui-switch-thumb-radius", "--cui-switch-thumb-background", "--cui-switch-inline-size", "--cui-switch-height", "--cui-switch-padding", "--cui-switch-thumb-inline-size", "--cui-switch-thumb-press-inline-size", "--cui-switch-thumb-press-scale", "--cui-switch-thumb-backdrop-filter"];
3
3
  export type SwitchKnobStyle = Partial<Record<(typeof switchKnobs)[number], string>>;
4
4
  //#endregion
@@ -16,6 +16,7 @@ const switchKnobs = [
16
16
  "--cui-switch-height",
17
17
  "--cui-switch-padding",
18
18
  "--cui-switch-thumb-inline-size",
19
+ "--cui-switch-thumb-press-inline-size",
19
20
  "--cui-switch-thumb-press-scale",
20
21
  "--cui-switch-thumb-backdrop-filter"
21
22
  ];
@@ -0,0 +1,22 @@
1
+ import { MarkdownImageUpload } from "./uploads.js";
2
+ import { Editor } from "@tiptap/core";
3
+ //#region src/markdown-editor/context.d.ts
4
+ export type MarkdownEditorContextValue = {
5
+ editor: Editor | null;
6
+ disabled: boolean;
7
+ source: boolean;
8
+ markdown: string;
9
+ sourceRequired: boolean;
10
+ setSource: (source: boolean) => void;
11
+ setMarkdown: (value: string) => void;
12
+ setContentAttributes: (attributes: Record<string, string>) => void;
13
+ canUpload: boolean;
14
+ uploads: MarkdownImageUpload[];
15
+ upload: (files: File[], position?: number) => void;
16
+ retryUpload: (id: string) => void;
17
+ removeUpload: (id: string) => void;
18
+ uploadError: string | null;
19
+ };
20
+ export declare const MarkdownEditorContext: import("react").Context<MarkdownEditorContextValue | null>;
21
+ export declare function useMarkdownEditor(): MarkdownEditorContextValue;
22
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from "react";
2
+ //#region src/markdown-editor/context.ts
3
+ const MarkdownEditorContext = createContext(null);
4
+ function useMarkdownEditor() {
5
+ const context = useContext(MarkdownEditorContext);
6
+ if (!context) throw new Error("Markdown editor parts require a MarkdownEditor parent.");
7
+ return context;
8
+ }
9
+ //#endregion
10
+ export { MarkdownEditorContext, useMarkdownEditor };
@@ -0,0 +1,10 @@
1
+ import { JSONContent, Node } from "@tiptap/core";
2
+ import { MarkdownManager } from "@tiptap/markdown";
3
+ //#region src/markdown-editor/extensions.d.ts
4
+ export declare function isEditorUrl(value: string, image?: boolean): boolean;
5
+ export declare const ImageUpload: Node<any, any>;
6
+ export declare function markdownExtensions(): (import("@tiptap/core").Extension<import("@tiptap/markdown").MarkdownExtensionOptions, import("@tiptap/markdown").MarkdownExtensionStorage> | import("@tiptap/core").Extension<import("@tiptap/starter-kit").StarterKitOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extension-table").TableKitOptions, any> | Node<any, any> | Node<import("@tiptap/extension-image").ImageOptions, any> | Node<import("@tiptap/extension-paragraph").ParagraphOptions, any> | Node<import("@tiptap/extension-list").TaskItemOptions, any> | Node<import("@tiptap/extension-list").TaskListOptions, any>)[];
7
+ export declare function createMarkdownCodec(): MarkdownManager;
8
+ export declare function requiresMarkdownSource(markdown: string, codec: MarkdownManager): boolean;
9
+ export declare function hasMarkdownContent(document: JSONContent): boolean;
10
+ //#endregion
@@ -0,0 +1,139 @@
1
+ import { Node } from "@tiptap/core";
2
+ import Image from "@tiptap/extension-image";
3
+ import { TaskItem, TaskList } from "@tiptap/extension-list";
4
+ import Paragraph from "@tiptap/extension-paragraph";
5
+ import { TableKit } from "@tiptap/extension-table";
6
+ import { Markdown, MarkdownManager } from "@tiptap/markdown";
7
+ import StarterKit from "@tiptap/starter-kit";
8
+ //#region src/markdown-editor/extensions.ts
9
+ function isEditorUrl(value, image = false) {
10
+ if (!value || /[\s\\]/.test(value) || Array.from(value).some((character) => character.charCodeAt(0) < 32)) return false;
11
+ if (value.startsWith("/") && !value.startsWith("//")) return true;
12
+ if (!image && value.startsWith("#")) return true;
13
+ try {
14
+ const url = new URL(value);
15
+ return [
16
+ "https:",
17
+ "http:",
18
+ ...image ? [] : ["mailto:"]
19
+ ].includes(url.protocol);
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+ const SafeImage = Image.extend({
25
+ renderMarkdown: (node) => {
26
+ const src = String(node.attrs?.src ?? "").replace(/[()]/g, (character) => `\\${character}`);
27
+ const alt = String(node.attrs?.alt ?? "").replace(/[\\[\]]/g, (character) => `\\${character}`);
28
+ const title = String(node.attrs?.title ?? "").replace(/[\\"]/g, (character) => `\\${character}`);
29
+ return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})`;
30
+ },
31
+ addAttributes() {
32
+ return {
33
+ ...this.parent?.(),
34
+ src: {
35
+ default: null,
36
+ parseHTML: (element) => {
37
+ const src = element.getAttribute("src") ?? "";
38
+ return isEditorUrl(src, true) ? src : null;
39
+ },
40
+ renderHTML: (attributes) => ({ src: isEditorUrl(attributes.src ?? "", true) ? attributes.src : void 0 })
41
+ }
42
+ };
43
+ }
44
+ });
45
+ const MarkdownParagraph = Paragraph.extend({ parseMarkdown: (token, helpers) => {
46
+ const content = helpers.parseInline(token.tokens ?? []);
47
+ const first = content[0];
48
+ const empty = content.length === 1 && first?.type === "text" && ["&nbsp;", "\xA0"].includes(first.text ?? "");
49
+ return helpers.createNode("paragraph", void 0, empty ? [] : content);
50
+ } });
51
+ const ImageUpload = Node.create({
52
+ name: "imageUpload",
53
+ group: "block",
54
+ atom: true,
55
+ selectable: true,
56
+ addAttributes: () => ({
57
+ id: { default: null },
58
+ name: { default: "Image" }
59
+ }),
60
+ renderHTML: ({ node }) => [
61
+ "div",
62
+ {
63
+ "data-image-upload": "",
64
+ role: "status"
65
+ },
66
+ `Image: ${node.attrs.name}`
67
+ ],
68
+ renderMarkdown: () => ""
69
+ });
70
+ function markdownExtensions() {
71
+ return [
72
+ StarterKit.configure({
73
+ underline: false,
74
+ paragraph: false,
75
+ link: {
76
+ openOnClick: false,
77
+ isAllowedUri: (url) => isEditorUrl(url)
78
+ }
79
+ }),
80
+ MarkdownParagraph,
81
+ SafeImage.configure({
82
+ allowBase64: false,
83
+ inline: true
84
+ }),
85
+ TaskList,
86
+ TaskItem.configure({
87
+ nested: true,
88
+ HTMLAttributes: { "data-type": "taskItem" }
89
+ }),
90
+ TableKit.configure({ table: { resizable: false } }),
91
+ ImageUpload,
92
+ Markdown
93
+ ];
94
+ }
95
+ function createMarkdownCodec() {
96
+ return new MarkdownManager({ extensions: markdownExtensions() });
97
+ }
98
+ const supportedTokens = /* @__PURE__ */ new Set([
99
+ "space",
100
+ "code",
101
+ "heading",
102
+ "hr",
103
+ "blockquote",
104
+ "list",
105
+ "list_item",
106
+ "paragraph",
107
+ "text",
108
+ "escape",
109
+ "link",
110
+ "image",
111
+ "strong",
112
+ "em",
113
+ "codespan",
114
+ "br",
115
+ "del",
116
+ "table",
117
+ "def",
118
+ "taskList",
119
+ "taskItem"
120
+ ]);
121
+ function unsupportedToken(value) {
122
+ if (Array.isArray(value)) return value.some(unsupportedToken);
123
+ if (typeof value !== "object" || value === null) return false;
124
+ if ("type" in value && typeof value.type === "string") {
125
+ if (!supportedTokens.has(value.type)) return true;
126
+ if ((value.type === "image" || value.type === "link") && "href" in value && typeof value.href === "string" && !isEditorUrl(value.href, value.type === "image")) return true;
127
+ }
128
+ return Object.values(value).some(unsupportedToken);
129
+ }
130
+ function requiresMarkdownSource(markdown, codec) {
131
+ return unsupportedToken(codec.instance.lexer(markdown));
132
+ }
133
+ function hasMarkdownContent(document) {
134
+ if (document.type === "image") return true;
135
+ if (document.type === "text" && document.text?.trim()) return true;
136
+ return document.content?.some(hasMarkdownContent) ?? false;
137
+ }
138
+ //#endregion
139
+ export { ImageUpload, createMarkdownCodec, hasMarkdownContent, isEditorUrl, markdownExtensions, requiresMarkdownSource };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/markdown-editor/toolbar.d.ts
3
+ export declare function MarkdownEditorToolbar({ children }: {
4
+ children?: ReactNode;
5
+ }): import("react").JSX.Element;
6
+ export declare function MarkdownEditorLink(): import("react").JSX.Element;
7
+ export declare function MarkdownEditorImage(): import("react").JSX.Element;
8
+ export declare function MarkdownEditorUploads(): import("react").JSX.Element;
9
+ //#endregion